import random

import numpy as np


class MGA:
    def __init__(self, crossover_prob=0.8, mutation_prob=0.1, pop_size=10, chrom_size=8, select_ratio=0.01,
                 generation=500):
        self._crossover_prob = crossover_prob
        self._mutation_prob = mutation_prob
        self._pop_size = pop_size
        self._chrom_size = chrom_size
        self._select_ratio = select_ratio
        self._generation = generation
        self._population = []

    def init_population(self, row_num=10, col_num=8):
        population = np.zeros([row_num, col_num], int)
        for index in range(0, row_num):
            for _index in range(0, col_num):
                population[index, _index] = np.random.choice([0, 1])
        self._population = population
        pass

    # 计算适应度
    def cal_fitness(self, input, error=0.01):
        fitness_val = 1
        fitness_val = np.sum(input) * 2
        return fitness_val

    # 进化-生成pop_size个新对象，并在生成的时候染色体进行交叉和随机产生变异
    def evolve(self, mother, father):
        new_population_list = []
        for i in range(0, self._pop_size):
            new_chromosome = self.crossover_and_mutation(mother, father)
            new_chromosome_list = new_chromosome.tolist()
            new_population_list.append(new_chromosome_list)
        new_population = np.asarray(new_population_list)
        # 重新填充
        self._population = new_population.copy()
        pass

    # 评估和选择，使用轮盘赌，返回选择的在population中的个体
    def select(self):
        # 每个个体适应度
        every_fitness = []
        for i in range(self._pop_size):
            each_every_fitness_value = self.cal_fitness(self._population[i])
            every_fitness.append(each_every_fitness_value)
        # 所有的适应度
        total_fitness = sum(every_fitness)

        # 下面是用轮盘赌的方式选择个体（复杂版，现在使用简洁版）
        # 得到各个个体的生存概率
        # selector_probability = []
        # for i in range(self._pop_size):
        #     each_selector_probability_value = every_fitness[i] / float(total_fitness)
        #     selector_probability.append(each_selector_probability_value)
        #
        # # 将个体的生存概率进行叠加，从而计算出各个个体的选择概率
        # for i in range(1, self._pop_size):
        #     selector_probability[i] = selector_probability[i] + selector_probability[i - 1]
        # 轮盘赌的方式选取，可参考轮盘赌
        # (t, idx) = (random.random(), 0)
        # for p in selector_probability:
        #     if p > t:
        #         break
        #     idx += 1
        # 轮盘赌的简洁写法
        ids = np.random.choice(np.arange(self._pop_size), size=1, replace=True,
                               p=every_fitness / total_fitness)
        select_chromosome = self._population[ids[0]]
        return select_chromosome

    def crossover_and_mutation(self, chromosome1, chromosome2):
        m_crossover_prob = random.random()
        new_chromosome = []
        # 如果生成的0-1区间的值小于交叉概率则父母交叉，不然就只保留其中一个
        if m_crossover_prob <= self._crossover_prob:
            # 随机点位，用于基金的单点交叉
            p = random.randint(1, self._chrom_size - 1)
            fragment1_1 = chromosome1[:p]
            fragment1_2 = chromosome1[p:]
            fragment2_1 = chromosome2[:p]
            fragment2_2 = chromosome2[p:]
            new_chromosome1 = np.hstack((fragment1_1, fragment2_2))
            new_chromosome2 = np.hstack((fragment2_1, fragment1_2))
            # 用概率来选择其中一个儿子
            select_prob = random.random()
            if select_prob >= 0.5:
                new_chromosome = new_chromosome1
            else:
                new_chromosome = new_chromosome2
            # 暂没实现多点交叉 _crossover_prob，以后实现

            # 这里进行变异，如果没有杂交则不变异
            new_chromosome = self.mutation(new_chromosome)

        else:
            # 用概率来选择其中一个儿子
            select_prob = random.random()
            if select_prob >= 0.5:
                new_chromosome = chromosome1
            else:
                new_chromosome = chromosome2

        return new_chromosome

    # 变异，这里选择的是二进制的变异
    def mutation(self, chromosome):
        if np.random.rand() < self._mutation_prob:
            # 随机产生一个实数，代表要变异基因的位置
            mutate_points = np.random.randint(0, self._chrom_size)
            # 将变异点位置的二进制反转
            chromosome[mutate_points] = chromosome[mutate_points] ^ 1
        return chromosome

    def run(self):
        # 先初始化种群
        self.init_population(row_num=self._pop_size, col_num=self._chrom_size)
        for each_gen in range(0, self._generation):
            # 选择两个最好的染色体
            chromosome1 = self.select()
            chromosome2 = self.select()
            # 两个染色体交叉、变异，并重新填充population族群
            self.evolve(chromosome1, chromosome2)

            print('each_gen' + str(each_gen) + ',' + str(self.cal_fitness(chromosome1)))

        final_chromosome1 = self.select()
        final_chromosome2 = self.select()
        print(final_chromosome1, final_chromosome2)
        fitness1 = self.cal_fitness(final_chromosome1)
        print(fitness1)

        pass


if __name__ == '__main__':
    mga = MGA(crossover_prob=0.8, mutation_prob=0.1, pop_size=10, chrom_size=8, select_ratio=0.1, generation=1000)
    mga.run()

    # p = 7
    # chromosome1 = [1, 2, 3, 4, 5, 6, 7, 8]
    # fragment1_1 = chromosome1[:p]
    # fragment1_2 = chromosome1[p:]
    # print(fragment1_1,fragment1_2)

    pass
